/*
Directory.cpp
*/

#include "FileSystem.h"
#include "Directory.h"

/*
Before using a newly created directory, check that the inode is not NULL.
*/
Directory::Directory(FileSystem * fs_loc,unsigned int inode_id_loc):
    File(fs_loc,inode_id_loc)
{
    populate_entries();
}

/*
Desc:
   Changes this directory to one of its immediate children. 
Args:
    Pass the name of a directory in THIS directory.
    The name is an actual name (not a path to a lower level).
    For example, if this is the root directory and the directory structure looks like:
    /
    /a
    /a/b
    You could pass this 'a', but not 'a/b'
Returns:
    0 on success
    -1 on error
*/
int Directory::CdChildDirectory(string name){

    map <string, unsigned int>::iterator it;

    it = entries.find(name);
    if(it == entries.end()){
        printf("Error Directory::CdChildDirectory - child directory '%s' not found\n",name.c_str());
        return -1;
    }

    set_inode(it->second);
    populate_entries();

    return 0;
}

/*
Desc:
    returns a File * of an 'immediate child' file located in this directory
    Creates a new File (that should be deleted somewhere else)
Args:
    name - the name of a file located directly in this directory (it can't be located in a sub directory of this directory)
Returns:
    NULL on error
    File * on success
*/
File * Directory::GetChildFile(string name){

    map <string, unsigned int>::iterator it;
    File * file;

    it = entries.find(name);
    if(it == entries.end()){
        printf("Error Directory::GetChildFile '%s' not found\n",name.c_str());
        return NULL;
    }

    //get the file (based on its inode that we just found)
    file = new File(fs,it->second);

    return file;
}

/*
Adds a file's name/inode in the directory
Increments the file's link_count 
*/
int Directory::AddEntry(string name, unsigned int inode_id){

    int i;
    unsigned int dir_entry_size; //number of bytes for each directory entry (for the name and inode id)
    char * dir_entry;
    char * new_dir_entry;
    map <string, unsigned int>::iterator it; //key = filename, val = inode number
    File * file;

    if(name.length() == 0){
        printf("Error Directory::AddEntry - empty name\n");
        return -1;
    }

    dir_entry_size = Settings::MaxNameLength+1+sizeof(unsigned int); //+1 is for the null char.
    dir_entry = (char *)malloc(sizeof(char)*dir_entry_size);
    new_dir_entry = (char *)malloc(sizeof(char)*dir_entry_size);
    //initialize so valgrind stops complaining
    bzero(new_dir_entry,sizeof(char)*dir_entry_size);

    if(name.length() > Settings::MaxNameLength){
        name.resize(Settings::MaxNameLength);
        printf("Error Directory::AddEntry - directory name is too long, trimming to first %u characters (%s)\n",Settings::MaxNameLength,name.c_str());
    }

    //check if a sub directory or file already has this name in the directory
    it = entries.find(name);
    if(it != entries.end()){
        printf("Error Directory::AddEntry - duplicate file or sub-directory (%s)\n",name.c_str());
        free(dir_entry);
        free(new_dir_entry);
        return -1;
    }

    //construct the new directory entry
    memcpy(new_dir_entry,name.c_str(),name.length()+1);
    //add the inode_id
    memcpy(new_dir_entry+(dir_entry_size-sizeof(unsigned int)),&inode_id,sizeof(unsigned int));

    //go to beginning of the directory's data
    i = Seek(0);
    if(i != 0){
        free(dir_entry);
        free(new_dir_entry);
        return -1;
    }
    //update the file's inode (increment number of links)
    file = new File(fs, inode_id);
    file->inode->link_count++;
    i = fs->write_inode(file);
    delete file;
    if(i != 0){
        free(dir_entry);
        free(new_dir_entry);
        return -1;
    }

    //first, read through the directory's data to see if there is an entry that can be overwritten
    while(Read(dir_entry_size,dir_entry) == dir_entry_size){
        //check if the file/subdir name equals ""
        if(dir_entry[0] == '\0'){
            //found an available location to write this entry to
            //adjust the ptr back from the bytes that we just read
            Seek(cur_file_byte-dir_entry_size);
            Write(dir_entry_size,new_dir_entry);
            free(dir_entry);
            free(new_dir_entry);
            entries.insert(make_pair(name,inode_id));
            return 0;
        }
    }

    /*
    if we got here, then we did not find a previously used entry
    the directory's position pointer is already at the end, so just write
    */
    Write(dir_entry_size,new_dir_entry);

    free(dir_entry);
    free(new_dir_entry);
    entries.insert(make_pair(name,inode_id));
    return 0;
}

/*
Removes a file's name/inode in the directory
*/
int Directory::RemoveEntry(string name){

    unsigned int dir_entry_size; //number of bytes for each directory entry (for the name and inode id)
    char * new_dir_entry;
    char * dir_entry;
    map <string, unsigned int>::iterator it;
    File * file;
    int entry_inode;
    int i;
    unsigned int r;

    it = entries.find(name);
    if(it == entries.end()){
        printf("Error Directory::RemoveEntry '%s' not found\n",name.c_str());
        return -1;
    }
    entry_inode = it->second;

    //remove from the map (easy)
    entries.erase(it);

    /*
    Now, we remove the entry from the directory's data block (harder)
    The name is set to '\0'. Then if we later add entries to the directory,
    we will reuse '\0' entries.
    */
    //go to beginning of the directory's data
    i = Seek(0);
    if(i != 0){
        return -1;
    }

    dir_entry_size = Settings::MaxNameLength+1+sizeof(unsigned int); //+1 is for the null char.
    dir_entry = (char *)malloc(sizeof(char)*dir_entry_size);

    new_dir_entry = (char *)malloc(sizeof(char)*dir_entry_size);
    //initialize so valgrind stops complaining
    bzero(new_dir_entry,sizeof(char)*dir_entry_size);

    //read through the directory's data to find this entry
    while(Read(dir_entry_size,dir_entry) == dir_entry_size){
        //check if the file/subdir name equals the name we are looking for
        if(strcmp(dir_entry,name.c_str())==0){

            if(cur_file_byte == inode->size){
                //this entry is the last in the list

                //actually decrease the directory size
                inode->size -= dir_entry_size;

                //re-calculate the number of blocks
                inode->num_blocks = (inode->size + Settings::BlockSize-1)/Settings::BlockSize;

                /*
                the ptrs will remain the same (possibly containing an extra block address)
                However, the addresses are read according to the number of blocks
                */
                fs->write_inode(inode_id,inode);

                free(dir_entry);
                free(new_dir_entry);

            }else{

                //the entry is not the last in the list

                //adjust the ptr back from the bytes that we just read
                Seek(cur_file_byte-dir_entry_size);
                //write the blank entry
                r = Write(dir_entry_size,new_dir_entry);
                free(dir_entry);
                free(new_dir_entry);
                if(r != dir_entry_size){
                    printf("Error Directory::RemoveEntry could not Write a blank directory entry\n");
                    return -1;
                }

            }

            //decrement the number of links to this file
            file = new File(fs,entry_inode);
            file->inode->link_count--;
            i = fs->write_inode(file);
            delete file;

            if(i != 0){
                return -1;
            }

            return 0;
        }
    }

    free(dir_entry);
    free(new_dir_entry);
    printf("Error Directory::RemoveEntry - could not find entry to file %s in the directory's data block\n",name.c_str());
    return -1;
}

/*
fills entries with the sub-directories and files in this directory
called when created and when cd to a child dir
data_block_ids is already set up
*/
void Directory::populate_entries(){

    unsigned int i;
    unsigned int dir_entry_size; //number of bytes for each directory entry (for the name and inode id)
    char * dir_entry;
    dir_entry_size = Settings::MaxNameLength+1+sizeof(unsigned int); //+1 is for the null char.
    dir_entry = (char *)malloc(sizeof(char)*dir_entry_size);
    string str_entry;
    unsigned int loc_inode_id;

    entries.clear();

    Seek(0);
    
    //for all the data blocks...
    //cout << "POPULATING entries of inode #: " << inode_id << " , number of blocks is " << data_block_ids.size() << endl;
    for(i=0;i<data_block_ids.size();i++){

        //read string/unsigned int pairs from the data blocks
        while(Read(dir_entry_size,dir_entry) == dir_entry_size){
            //check if the file/subdir name equals ""
            //cout << "\t" << dir_entry << endl;
            if(dir_entry[0] == '\0'){
                //blank entry (from a file that was unlinked) or directory that was removed
                continue;
            }
            str_entry = dir_entry;
            memcpy(&loc_inode_id,dir_entry+dir_entry_size-sizeof(unsigned int),sizeof(unsigned int));
            entries.insert(make_pair(str_entry,loc_inode_id));    
        }
    }

    free(dir_entry);

}
